package cn.amossun.starter.mybatis.data.security.rule;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.amossun.starter.common.crypto.rule.DecryptRule;
import cn.amossun.starter.mybatis.data.security.config.MybatisPlusDataSecurityConfig;
import cn.amossun.starter.mybatis.data.security.runner.OrmConfigCacheRunner;
import cn.amossun.starter.mybatis.data.security.enums.ConfigTypeEnum;
import cn.amossun.starter.mybatis.data.security.properties.*;
import cn.amossun.starter.common.util.DesensitizedExecutor;
import cn.amossun.starter.redis.cache.exception.RedisCacheConfigException;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 解密规则
 */
@Slf4j
public class MybatisDecryptRule extends DecryptRule {

    public MybatisDecryptRule(MybatisPlusDataSecurityConfig mybatisConfig, OrmConfigCacheRunner ormConfigCacheRunner) throws Exception {
        super(mybatisConfig.getMybatisPlus());
        this.mybatisConfig = mybatisConfig;
        this.ormConfigCacheRunner = ormConfigCacheRunner;
        this.ormConfigMap = buildMapByTableName(getOrmConfigs());
    }

    private final MybatisPlusDataSecurityConfig mybatisConfig;

    private final OrmConfigCacheRunner ormConfigCacheRunner;

    /**
     * 加解密ORM配置集合
     */
    private Map<String, ResultMapConfiguration> ormConfigMap;

    private volatile Map<String, ResultMapConfiguration> decryptRuleMap;


    public boolean updateOrmConfigMap() throws Exception {
        this.ormConfigMap =  buildMapByTableName(getOrmConfigs());
        return true;
    }


    public Map<String, ResultMapConfiguration> getOrmConfigMap() throws Exception {
        updateOrmConfigMap();
        return this.ormConfigMap;
    }

    public void refreshDecryptRuleMap() throws Exception {
        Map<String, ResultMapConfiguration> decryptRuleMap = getOrmConfigMap();
        if(CollectionUtil.isNotEmpty(decryptRuleMap)) {
            this.decryptRuleMap = decryptRuleMap;
        }
        if(CollectionUtil.isEmpty(decryptRuleMap)) {
            this.decryptRuleMap = MapUtil.newHashMap(0);
        }
    }

    public Map<String, ResultMapConfiguration> getDecryptRuleMap() {
        return this.decryptRuleMap;
    }

    protected List<ResultMapConfiguration> getOrmConfigs() throws ExecutionException, RedisCacheConfigException {
        if(mybatisConfig.getMybatisPlus() == null) {
            log.warn("mybatisPlus为空.");
            return null;
        }
        if(mybatisConfig.getMybatisPlus().getConfigType() == ConfigTypeEnum.YML) {
            log.info("配置redis json格式为yml.");
            return mybatisConfig.getMybatisPlus().getOrmConfigs();
        }
        if(mybatisConfig.getMybatisPlus().getConfigType() == ConfigTypeEnum.REDIS) {
            Object object = ormConfigCacheRunner.getCacheValue();
            if(object != null && object instanceof Optional && ((Optional<Object>)object).isPresent()) {
                return JSONUtil.toList(JSONUtil.parseArray(((Optional<Object>)object).get()), ResultMapConfiguration.class);
            }
            log.warn("redis json格式配置redis为空.");
            return null;
        }
        log.warn("未配置redis json格式.");
        return null;
    }

    protected Map<String, ResultMapConfiguration> buildMapByTableName(List<ResultMapConfiguration> ormConfigs) {
        if(CollectionUtil.isEmpty(ormConfigs)) {
            log.warn("获取redis加解密配置json 为空.");
            return MapUtil.newHashMap(0);
        }

        Map<String, ResultMapConfiguration> resultMapConfigurationMap = new HashMap<>(ormConfigs.size());
        ormConfigs.stream().forEach(source->{
            Map<String, ResultConfiguration> propertyMap = source.getResults().stream().collect(Collectors.toMap(ResultConfiguration::getProperty, Function.identity(), (exiting, replacement) -> exiting));

            ResultMapConfiguration resultMapConfiguration = new ResultMapConfiguration();
            resultMapConfiguration.setObjectClassName(source.getObjectClassName());
            resultMapConfiguration.setDecryptVersion(source.getDecryptVersion());
            resultMapConfiguration.setPropertyMap(propertyMap);
            resultMapConfigurationMap.put(source.getObjectClassName(), resultMapConfiguration);
        });
        return resultMapConfigurationMap;
    }

    protected Optional<ResultConfiguration> getChildRuleConfiguration(String objectName, String filedName) throws Exception {

        if (!getDecryptRuleMap().containsKey(objectName)) {
            return Optional.empty();
        }

        final ResultMapConfiguration resultMapConfiguration = getDecryptRuleMap().get(objectName);

        final Map<String, ResultConfiguration> propertyMap = resultMapConfiguration.getPropertyMap();

        if (!propertyMap.containsKey(filedName)) {
            return Optional.empty();
        }
        return Optional.of(propertyMap.get(filedName));
    }

    public Optional<ResultConfiguration> checkObjectValue(String objectName, String filedName) throws Exception {

        final Optional<ResultConfiguration> resultConfigurationOptional = getChildRuleConfiguration(objectName, filedName);
        if (!resultConfigurationOptional.isPresent()) {
            return null;
        }
        return resultConfigurationOptional;
    }

    public Object decryptObjectValue(Optional<ResultConfiguration> resultConfigurationOptional, Object originalValue) throws Exception {
        if (!resultConfigurationOptional.isPresent()) {
            return originalValue;
        }

        final ResultConfiguration configuration = resultConfigurationOptional.get();

        originalValue = decryptValue(configuration, originalValue);

        return originalValue;
    }

    public Object getDecryptValue(ResultConfiguration configuration, Object originalValue) throws BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, UnsupportedEncodingException {
        if (!(originalValue instanceof String) && !(originalValue instanceof Number)) {
            return originalValue;
        }
        if(configuration != null && StrUtil.isNotEmpty(configuration.getEncryptDelimiter())) {
            List<String> originalValueList = Arrays.asList((String.valueOf(originalValue)).split(configuration.getEncryptDelimiter()));
            List<String> decryptValueList = new ArrayList<>(originalValueList.size());
            for (String sourceStr : originalValueList) {
                decryptValueList.add(getDecryptValue(sourceStr));
            }
            return StrUtil.join(configuration.getEncryptDelimiter(), decryptValueList);
        }
        return getDecryptValue(String.valueOf(originalValue));
    }

    protected Object decryptValue(ResultConfiguration configuration, Object originalValue) throws BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, UnsupportedEncodingException {
        if (configuration.isDecrypt()) {

            String decryptStr = String.valueOf(getDecryptValue(configuration, originalValue));
            //是否解密后脱敏
            if (configuration.getDesensitizedType() != null) {
                return DesensitizedExecutor.desensitized(configuration.getDesensitizedType(), decryptStr);
            }
            return decryptStr;
        }

        //是否直接脱敏
        if (configuration.getDesensitizedType() != null) {
            return DesensitizedExecutor.desensitized(configuration.getDesensitizedType(), String.valueOf(originalValue));
        }
        return originalValue;
    }

}
